﻿/******************************************************************************
 * GSearch.cs
 * 
 * This module implements the GSearchCore base search class and related types.
 *  
 * Date:   12/2008
 * 
 * Copyright (c) 2009, Mark Betz 
 * 
 * All rights reserved. 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met: 
 * 
 *   * Redistributions of source code must retain the above copyright 
 *     notice, this list of conditions and the following disclaimer. 
 *   * Redistributions in binary form must reproduce the above copyright 
 *     notice, this list of conditions and the following disclaimer in the 
 *     documentation and/or other materials provided with the distribution. 
 *   * Neither the name of the Author nor the names of contributors may be 
 *     used to endorse or promote products derived from this software
 *     without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY MARK BETZ ''AS IS'' AND ANY  EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL MARK BETZ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 * 
 */
using System;
using System.Globalization;
using System.IO;
using System.Runtime.Serialization.Json;

namespace GSearch
{
    /// <summary>
    /// The SearchStatus enum contains codes that mark the stages of the
    /// image search progress. These are passed to the subscriber
    /// when the SearchProgressChanged event is raised.
    /// </summary>
    public enum SearchStatus
    {
        /// <summary>
        /// Signifies that the search process is connecting to Google servers
        /// </summary>
        Connecting,
        
        /// <summary>
        /// Signifies that the search process has received results from Google
        /// </summary>
        ResultsReceived,
        
        /// <summary>
        /// Signifies that processing of search results is complete
        /// </summary>
        Complete,
        
        /// <summary>
        /// Signifies that an error has occured during the search
        /// </summary>
        Error
    };

    /// <summary>
    /// SearchEventArgs is used in the SearchProgressChanged event, and
    /// is the base class for all search-specific event classes.
    /// </summary>
    public class SearchEventArgs : EventArgs
    {
        /// <summary>
        /// Default constructor for SearchEventArgs class.
        /// </summary>
        public SearchEventArgs()
        {
            Status = SearchStatus.Connecting;
            Result = null;
            Ex = null;
        }
        
        /// <summary>
        /// Creates a new SearchEventArgs object given the passed arguments
        /// </summary>
        /// <param name="s"><see cref="GSearch.SearchStatus"/> enum value specifying the status</param>
        /// <param name="result"><see cref="GSearch.GResponse"/> containing the result when
        /// Status == Complete. Must be cast to the appropriate response type 
        /// before accessing./></param>
        /// <param name="ex">A System.Exception when Status == Error</param>
        public SearchEventArgs( SearchStatus s, GResponse result, Exception ex )
        {
            Status = s;
            Result = result;
            Ex = ex;    
        }
        
        /// <summary>
        /// A value from the <see cref="GSearch.SearchStatus"/> enum giving
        /// the current status of an asyncronous search.
        /// </summary>
        public SearchStatus Status { get; set; }
        
        /// <summary>
        /// Reference to a System.Exception provided when Status == Error,
        /// otherwise this is null.
        /// </summary>
        public Exception Ex { get; set; }
        
        /// <summary>
        /// Reference to a <see cref="GSearch.GResponse"/> object containing
        /// the results of a search, provided when Status == Complete. For
        /// all other types of event this property is null.
        /// </summary>
        public GResponse Result { get; set; }
    }

    /// <summary>
    /// enumerates the types of searches available. Used by the unified
    /// interface.
    /// </summary>
    public enum SearchType
    {
        /// <summary>
        /// Specifies a web search
        /// </summary>
        Web,
        
        /// <summary>
        /// Specifies an image search
        /// </summary>
        Image,
        
        /// <summary>
        /// Specifies a video search
        /// </summary>
        Video,
        
        /// <summary>
        /// Specifies a book search
        /// </summary>
        Book,
        
        /// <summary>
        /// Specifies a local search
        /// </summary>
        Local,
        
        /// <summary>
        /// Specifies a news search
        /// </summary>
        News,
        
        /// <summary>
        /// Specifies a blog search
        /// </summary>
        Blog,
        
        /// <summary>
        /// Specifies a patent search
        /// </summary>
        Patent
    }

    /// <summary>
    /// Enumerates the levels of search filtering available for images,
    /// and web results. Off: no filtering; Moderate: some filtering;
    /// Full: strict filtering. The filter mechanism attempts to remove
    /// offensive images from the results.
    /// </summary>
    public enum SearchSafety
    {
        /// <summary>
        /// Signifies safe search filtering should be turned off
        /// </summary>
        Off,
        
        /// <summary>
        /// Signifies a moderate level of safe search filtering
        /// </summary>
        Moderate,
        
        /// <summary>
        /// Signifies full safe search filtering
        /// </summary>
        Full
    };

    /// <summary>
    /// Enumerates the choices for result set size, where Small returns four
    /// results at a time, and Large returns eight.
    /// </summary>
    public enum SearchResultSize
    {
        /// <summary>
        /// Signifies that each result page should have 4 entries
        /// </summary>
        Small,
        
        /// <summary>
        /// Signifies that each result page should have 8 entries
        /// </summary>
        Large
    };

    /// <summary>
    /// Enumerates the available result ranking methods , where Relevance
    /// is the default ranking.
    /// </summary>
    public enum SearchScoring
    {
        /// <summary>
        /// Signifies that search results should be ranked on relevance
        /// </summary>
        Relevance,
        
        /// <summary>
        /// Signifies that search results should be ranked by date
        /// </summary>
        Date,
        
        /// <summary>
        /// Signifies that search results should be ranked by date
        /// ascending - not all search types support this value
        /// </summary>
        DateAscending
    };

    /// <summary>
    /// Base class containing the common search arguments
    /// </summary>
    public abstract class SearchArgs
    {
        /// <summary>
        /// Default constructor for SearchArgs class
        /// </summary>
        public SearchArgs()
        {
            Terms = "";
            ResultSize = SearchResultSize.Small;
            ApiKey = "";
            StartIndex = 0;
            HostLang = SearchHostLang.English;
        }
        
        /// <summary>
        /// ToQueryString converts the current state of the SearchArgs members
        /// to the appropriate query string. This method is overridden by derived
        /// classes, and explicitly called from the derived implementation to
        /// get the first part of the query.
        /// </summary>
        /// <returns>A System.String containing the base querystring arguments for
        /// the specified search.</returns>
        public virtual string ToQueryString()
        {
            string qry = "?q=" + Uri.EscapeUriString(Terms);
            qry += "&v=" + Version;
            qry += "&rsz=" + ArgStrings.rsmap[Convert.ToInt32(ResultSize)];
            qry += "&hl=" + ArgStrings.hlmap[Convert.ToInt32(HostLang)];
            if ( "" != ApiKey )
                qry += "&key=" + ApiKey;
            if ( 0 != StartIndex )
                qry += "&start=" 
                + StartIndex.ToString(CultureInfo.InvariantCulture.NumberFormat);
                
            return qry;
        }
        
        /// <summary>
        /// A System.String containing the search query terms, unescaped.
        /// </summary>
        public string Terms { get; set; }
        
        /// <summary>
        /// A System.String containing the version of the API the program was
        /// written for. Always set to "1.0".
        /// </summary>
        public string Version { get { return _ver; } }
        
        /// <summary>
        /// A value from the <see cref="GSearch.SearchResultSize"/> enum giving
        /// the size of result set desired. The Google API returns up to eight
        /// pages of results. For Large result sets each page contains 8 entries
        /// for a total of 64. For small sets there are 4 entries per page for
        /// a total of 32.
        /// </summary>
        public SearchResultSize ResultSize { get; set; }
        
        /// <summary>
        /// A value from the <see cref="GSearch.SearchHostLang"/> enum giving
        /// the culture in which the program executing the search is running.
        /// </summary>
        public SearchHostLang HostLang { get; set; }
        
        /// <summary>
        /// A System.String containing the API key assigned to the application
        /// by Google. API keys are optional, and allow Google to track your
        /// application and notify you of problems. See the Google API 
        /// documentation for more information on API keys.
        /// </summary>
        public string ApiKey { get; set; }
        
        /// <summary>
        /// A System.Int32 describing the zero-based item index on which the first
        /// page (page 0) of results should start. If the result set size is
        /// Large this value must be a multiple of 8, else it must be a 
        /// multiple of 4.
        /// </summary>
        public int StartIndex { get; set; }
        
        /// <summary>
        /// Used to set the version argument to "1.0"
        /// </summary>
        private const string _ver = "1.0";
    }
    
    /// <summary>
    /// GSearchCore serves as the base class for the specific search engines, such as
    /// GSearch.GImageSearch. 
    /// </summary>
    public abstract class GSearchCore
    {
        /// <summary>
        /// The constructor creates an instance of the GSearchClient class which will
        /// be used later for executing queries.
        /// </summary>
        public GSearchCore()
        {
            _client = new GSearchClient(this);
            _client.SearchComplete += 
                new EventHandler<SearchClientEventArgs>(_client_SearchComplete);
            _client.SearchFailed += 
                new EventHandler<SearchClientEventArgs>(_client_SearchFailed);
        }
        
        /// <summary>
        /// Returns the service uri for the base search service. Derived types are
        /// responsible for overriding this and calling the base version in the
        /// override implementation to construct the entire service uri
        /// </summary>
        public virtual string ServiceURI
        {
            get { return "http://ajax.googleapis.com/ajax/services/search/"; }
        }

#if (!SILVERLIGHT)
        /// <summary>
        /// Uses the GSearchClient to perform a blocking read of the Google ajax
        /// web service. Derived types wrap these calls in a public interface
        /// that supports a specific type of SearchArgs, i.e. ImageSearchArgs. This
        /// method is not available in the Silverlight version of the libraries.
        /// </summary>
        /// <param name="args">Search arguments, derived from SearchArgs</param>
        /// <returns>Response of type GResponse. This reference must be cast to a
        /// specific search response type before results can be accessed.</returns>
        /// <exception cref="Exception">If an error occurs in GSearchClient.Search</exception>
        protected GResponse Search(SearchArgs args)
        {
            Stream s = null;
            try
            {
                s = _client.Search( this, args );
            }
            catch( Exception ex )
            {
                throw new Exception("GSearchClient.Search failed", ex);
            }
            
            try
            {
                return Deserialize(s);
            }
            catch( Exception ex )
            {
                throw new Exception("JSON Deserialization failed", ex);
            }
        }
#endif

        /// <summary>
        /// Uses the GSearchClient to perform an async read of the Google ajax
        /// web service. Derived types wrap these calls in a public interface
        /// that supports a specific type of SearchArgs, i.e. ImageSearchArgs.
        /// Results are returned in the arguments to SearchComplete and
        /// SearchProgressChanged.
        /// </summary>
        /// <param name="args">Search arguments, derived from <see cref="GSearch.SearchArgs"/></param>
        /// <exception cref="Exception">If an error occurs in GSearchClient.SearchAsync</exception>
        protected void SearchAsync(SearchArgs args)
        {
            OnSearchProgressChanged( new SearchEventArgs(SearchStatus.Connecting, null, null) );
            try
            {
                _client.SearchAsync( this, args );
            }
            catch (Exception ex)
            {
                throw new Exception("GSearchClient.SearchAsync failed", ex);
            }
        }

        /// <summary>
        /// Raises the SearchComplete event. Derived types must implement this method.
        /// </summary>
        /// <param name="e"><see cref="SearchEventArgs"/> containing 
        /// information about the event</param>
        protected abstract void OnSearchComplete(SearchEventArgs e);

        /// <summary>
        /// Raises the SearchFailed event. Derived types must implement this method.
        /// </summary>
        /// <param name="e"><see cref="SearchEventArgs"/> containing 
        /// information about the event</param>
        protected abstract void OnSearchFailed(SearchEventArgs e);

        /// <summary>
        /// Raises the SearchProgressChanged event. Derived types must implement this method.
        /// </summary>
        /// <param name="e"><see cref="SearchEventArgs"/> containing 
        /// information about the event</param>
        protected abstract void OnSearchProgressChanged(SearchEventArgs e);

        /// <summary>
        /// Deserialize() is called with a valid result stream. It uses the JSON
        /// data serializer to marshall the JSON result data from the stream into
        /// the data contract classes. Derived classes must implement.
        /// </summary>
        /// <param name="result">Stream containing the Google JSON data</param>
        /// <returns>Instance of GResponse</returns>
        protected abstract GResponse Deserialize(Stream result);

        
        /// <summary>
        /// DeserializeImpl does the actual work of deserializing the json objects
        /// in the result stream. Deserialization is very generic as long as we
        /// have the type, which we get from the derived class referring back to
        /// this method in its implementation of Deserialize(Stream). The big advantage
        /// of centralizing this code in the base is to be certain the result stream
        /// is closed. It also means we don't need to know about the serialization
        /// namespace in the derived classes.
        /// </summary>
        /// <typeparam name="T">GResponse</typeparam>
        /// <param name="result">Stream containing the json results, if any</param>
        /// <returns>An instance of a class derived from GResponse, as a reference to GResponse</returns>
        protected GResponse DeserializeImpl<T>(Stream result) where T : GResponse
        {
            DataContractJsonSerializer jsonSerializer =
                new DataContractJsonSerializer(typeof(T));

            T res = null;

            try
            {
                res = (T)jsonSerializer.ReadObject(result);
            }
            catch (Exception ex)
            {
                throw new Exception("Error deserializing JSON data", ex);
            }

            result.Close();

            return res;
        }
        
        /// <summary>
        /// Handles the SearchComplete event on the GSearchClient. Called when an async
        /// read of the Google service completes successfully. The event args contain
        /// a reference to the stream containing the results. The method raises
        /// SearchProgressChanged before and after deserialization, and then raises
        /// SearchComplete.
        /// </summary>
        /// <param name="sender">sending GSearchClient</param>
        /// <param name="e">SearchClientEventArgs</param>
        private void _client_SearchComplete( object sender, SearchClientEventArgs e )
        {
            SearchEventArgs se = new SearchEventArgs(SearchStatus.ResultsReceived, null, null);
            this.OnSearchProgressChanged( se );
            try
            {
                GResponse gr = Deserialize( e.result );
                se.Status = SearchStatus.Complete;
                se.Result = gr;
                this.OnSearchProgressChanged(se);
                this.OnSearchComplete(se);
            }
            catch( Exception ex )
            {
                Exception ex2 = new Exception("JSON Deserialization failed", ex);
                se.Status = SearchStatus.Error;
                se.Ex = ex2;
                this.OnSearchFailed(se);
            }
            
        }
        
        /// <summary>
        /// Handles the SearchFailed event on the GSearchClient. Called when an async
        /// read of the Google service fails. e.e contains the exception received from
        /// the WebClient. Raises SearchFailed.
        /// </summary>
        /// <param name="sender">sending GSearchClient</param>
        /// <param name="e">SearchClientEventArgs</param>
        private void _client_SearchFailed(object sender, SearchClientEventArgs e)
        {
            this.OnSearchFailed( new SearchEventArgs(SearchStatus.Error, null, e.e) );
        }
        
        private GSearchClient _client;
    }
}
